package restful

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"hash"
	"io"
	"moss/conf"
	"moss/meta"
	"moss/status"
	"net/http"
	"net/url"
	"strings"
	"time"
)

func checkPolicyCondsExact(req *http.Request, formData url.Values, v1, v2 string) status.Status {
	switch v1 {
	case conf.PolicyConditionsbucket:
		if v2 != getBucketName(req) {
			return status.AccessDenied
		}
	default:
		if v2 != formData.Get(v1) {
			return status.AccessDenied
		}
	}
	return status.Success
}

func checkPolicyCondsExactEq(req *http.Request, formData url.Values, v2, v3 string) status.Status {
	v := strings.TrimPrefix(v2, "$")
	switch v {
	case conf.PolicyConditionsbucket:
		if v3 != getBucketName(req) {
			return status.AccessDenied
		}
	default:
		if v3 != formData.Get(v) {
			return status.AccessDenied
		}
	}
	return status.Success
}

func checkPolicyCondsStartWith(formData url.Values, v2, v3 string) status.Status {
	v := strings.TrimPrefix(v2, "$")
	if !strings.HasPrefix(formData.Get(v), v3) {
		return status.AccessDenied
	}
	return status.Success
}

func checkPolicyCondsLengthRange(postFileSize int64, start, end float64) status.Status {
	if float64(postFileSize) > end || float64(postFileSize) < start {
		return status.AccessDenied
	}
	return status.Success
}

func checkPolicySignature(bucketName, policyEncodedStr, accessKeyId, signatureString string) status.Status {
	bucketInfo, ok := meta.GetBucketInfo(bucketName)
	if !ok {
		return status.NoSuchBucket
	}

	// According to Aliyun Doc: if permission is read-write
	// signature string can be discarded. Otherwise need validation
	if bucketInfo.Permission == conf.PermissionPublicReadWrite {
		return status.Success
	}

	accessKeySecret, ok := meta.GetAccessKeySecret(accessKeyId)
	if !ok {
		return status.InvalidAccessKeyId
	}

	h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(accessKeySecret))
	io.WriteString(h, policyEncodedStr)
	signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil))
	if signedStr != signatureString {
		return status.SignatureDoesNotMatch
	}

	return status.Success
}

func checkPolicy(req *http.Request, formData url.Values, filePostSize int64) status.Status {
	policyEncodedStr := formData.Get(conf.OssFormFieldPolicy)
	b, err := base64.StdEncoding.DecodeString(policyEncodedStr)
	if err != nil {
		return status.InternalError
	}

	var policy PostPolicy
	err = json.Unmarshal(b, &policy)
	if err != nil {
		return status.InvalidPolicyDocument
	}
	if policy.Expiration == "" || len(policy.Expiration) < 1 {
		return status.InvalidPolicyDocument
	}

	dateStr := req.Header.Get(conf.HTTPHeaderDate)
	if len(dateStr) == 0 {
		return status.AccessDenied
	}
	datePost, err := time.Parse(http.TimeFormat, dateStr)
	if err != nil {
		return status.AccessDenied
	}
	if len(policy.Expiration) == 0 {
		return status.AccessDenied
	}
	expiration, err := time.Parse(conf.OssTimeFormat, policy.Expiration)
	if err != nil {
		return status.AccessDenied
	}
	if datePost.After(expiration) {
		return status.AccessDenied
	}
	conditions := policy.Conditions
	if len(conditions) < 1 {
		return status.AccessDenied
	}

	for _, v := range conditions {
		switch x := v.(type) {
		case map[string]interface{}:
			if len(x) != 1 {
				return status.InvalidPolicyDocument
			}
			for k, v := range x {
				vx, ok := v.(string)
				if !ok {
					return status.InvalidPolicyDocument
				}

				rc := checkPolicyCondsExact(req, formData, k, vx)
				if rc != status.Success {
					return rc
				}
			}

		case []interface{}:
			if len(x) != 3 {
				return status.AccessDenied
			}
			vx, ok := x[0].(string)
			if !ok {
				return status.InvalidPolicyDocument
			}
			switch vx {
			case conf.PolicyConditionsContentLengthRange:
				vx2, ok := x[1].(float64)
				if !ok {
					return status.InvalidPolicyDocument
				}
				vx3, ok := x[2].(float64)
				if !ok {
					return status.InvalidPolicyDocument
				}

				rc := checkPolicyCondsLengthRange(filePostSize, vx2, vx3)
				if rc != status.Success {
					return rc
				}
			case conf.PolicyConditionsEq:
				vx2, ok := x[1].(string)
				if !ok {
					return status.InvalidPolicyDocument
				}
				vx3, ok := x[2].(string)
				if !ok {
					return status.InvalidPolicyDocument
				}

				rc := checkPolicyCondsExactEq(req, formData, vx2, vx3)
				if rc != status.Success {
					return rc
				}
			case conf.PolicyConditionsStartsWith:
				vx2, ok := x[1].(string)
				if !ok {
					return status.InvalidPolicyDocument
				}
				vx3, ok := x[2].(string)
				if !ok {
					return status.InvalidPolicyDocument
				}
				rc := checkPolicyCondsStartWith(formData, vx2, vx3)
				if rc != status.Success {
					return rc
				}
			}

		}
	}
	return status.Success
}

type PostPolicy struct {
	Expiration string        `json:"expiration"`
	Conditions []interface{} `json:"conditions"`
}
